Skip to content

feat(admin): add Billing Platform admin page with invoice comparison#116269

Merged
armcknight merged 3 commits into
masterfrom
andrewmcknight/reveng-86-invoice-evaluation
May 28, 2026
Merged

feat(admin): add Billing Platform admin page with invoice comparison#116269
armcknight merged 3 commits into
masterfrom
andrewmcknight/reveng-86-invoice-evaluation

Conversation

@armcknight
Copy link
Copy Markdown
Member

@armcknight armcknight commented May 27, 2026

Problem

Milestone-1 (consumer billing) ships invoices through the new platform code path in shadow mode alongside the legacy generator. Staff have no in-product way to spot-check parity between the two — confirming counts, dollar totals, and biggest divergences for invoices generated in a given window. Needed before we can confidently flip a population to platform-issued invoices.

Paired backend PR: getsentry#20425.

Solution

New "Billing Platform" page under `/_admin/billing-platform/`, surfaced as a sidebar entry between Invoices and Spike Projection Generation. First section is an Invoice Comparison tool; the page is intentionally a container for future milestone-1 verification tooling.

The tool itself:

  • Region dropdown + two `datetime-local` inputs (default: last 24 hours, local time).
  • On submit, local times are converted to UTC ISO strings and sent to the cell-scoped `/api/0/_admin/cells/<cell_id>/invoice-comparison/` endpoint.
  • Renders a summary card (legacy/platform counts, totals, total delta) and a per-org table with legacy $ + count, platform $ + count, delta, and a status `Tag` (`match` / `mismatch` / `legacy_only` / `platform_only`).
  • Rows sorted by absolute $ delta desc — biggest divergences first.

Notes

  • Backend endpoint lives in the paired getsentry PR; this PR adds the URL to `knownGetsentryApiUrls.ts` so `getApiUrl` typechecks.
  • `datetime-local` → UTC ISO conversion on submit avoids ambiguity over "last 24 hours since midnight UTC vs query time."
  • No frontend spec yet — the page is mostly presentation over a thoroughly-tested API. Happy to add one if requested.

REVENG-86

@armcknight armcknight requested a review from a team as a code owner May 27, 2026 00:16
@linear-code
Copy link
Copy Markdown

linear-code Bot commented May 27, 2026

REVENG-86

@github-actions github-actions Bot added the Scope: Frontend Automatically applied to PRs that change frontend components label May 27, 2026
@armcknight armcknight changed the title feat(admin): add Billing Platform admin page with invoice comparison (REVENG-86) feat(admin): add Billing Platform admin page with invoice comparison May 27, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 27, 2026

📊 Type Coverage Diff

Metric Before After Delta
Coverage 93.59% 93.59% ±0%
Typed 133,067 133,106 🟢 +39
Untyped 9,118 9,123 🔴 +5
🔍 5 new type safety issues introduced

any-typed symbols (5 new)

File Line Detail
static/gsAdmin/views/invoiceComparison.tsx 155 r (param)
static/gsAdmin/views/invoiceComparison.tsx 156 opt (param)
static/gsAdmin/views/invoiceComparison.tsx 157 r (param)
static/gsAdmin/views/invoiceComparison.tsx 192 error as any (as-any)
static/gsAdmin/views/invoiceComparison.tsx 193 error as any (as-any)

This is informational only and does not block the PR.

@armcknight
Copy link
Copy Markdown
Member Author

@sentry review

@armcknight
Copy link
Copy Markdown
Member Author

@sentry review

Comment thread static/gsAdmin/views/invoiceComparison.tsx
@armcknight armcknight force-pushed the andrewmcknight/reveng-86-invoice-evaluation branch from d91b0ed to c89cc4e Compare May 27, 2026 19:20
Comment thread static/gsAdmin/views/billingPlatform.tsx Outdated
Comment thread static/gsAdmin/views/invoiceComparison.tsx Outdated
@armcknight armcknight force-pushed the andrewmcknight/reveng-86-invoice-evaluation branch from c89cc4e to 8750267 Compare May 27, 2026 21:17
Comment thread static/gsAdmin/views/invoiceComparison.tsx Outdated
@armcknight armcknight force-pushed the andrewmcknight/reveng-86-invoice-evaluation branch from 8750267 to a8f90aa Compare May 27, 2026 21:30
@armcknight armcknight enabled auto-merge (squash) May 27, 2026 22:17
@armcknight armcknight force-pushed the andrewmcknight/reveng-86-invoice-evaluation branch from 795c6a2 to 30b04ee Compare May 27, 2026 22:46
@armcknight
Copy link
Copy Markdown
Member Author

Tried force-pushing a clean history on top of base to make jest tests happy. 🤞🏻

@armcknight armcknight force-pushed the andrewmcknight/reveng-86-invoice-evaluation branch from 30b04ee to 9db6f47 Compare May 28, 2026 17:02
@armcknight
Copy link
Copy Markdown
Member Author

tried squashing all commits to one, maybe the lookback on the base branch detection will be happy with that?

Comment thread static/gsAdmin/views/invoiceComparison.tsx Outdated
Andrew McKnight and others added 3 commits May 28, 2026 10:09
…G-86)

Adds a new "Billing Platform" sidebar entry hosting an Invoice Comparison
tool that calls the new
GET /api/0/_admin/cells/<cell_id>/invoice-comparison/ endpoint
(getsentry-side change in a paired PR).

The page lets staff pick a region + datetime-local time window and renders
per-org totals comparing legacy Invoice vs PlatformInvoice records
generated in the window, plus summary counts/totals/delta. Rows are
sorted by absolute $ delta desc. datetime-local inputs are converted to
UTC ISO on submit so the wire format is unambiguous regardless of the
operator's timezone.

The Billing Platform page is the landing spot for future milestone-1
verification tooling — invoice comparison is the first section.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

feat(admin): show % delta column in invoice comparison

Pairs with the getsentry-side switch to percent-based sorting. Adds a
"Δ %" column showing percent delta relative to legacy (or ∞ when there's
no legacy baseline). Keeps the "Δ $" column so absolute-dollar context
remains visible.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

fix(admin): right-align Δ columns in invoice comparison table

The Table descendant rule (`th, td { text-align: left }`) had
specificity (0,1,1) which beat the single-class RightHeader/RightCell
selectors at (0,1,0), so Legacy/Platform/Δ %/Δ $ columns silently
rendered left-aligned. Wrap the right-align rule in `&&` to double the
class selector and win specificity.

Addresses Cursor Bugbot finding.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

ref(admin): use scraps core components in Billing Platform page

Replaces custom styled() helpers with scraps primitives per AGENTS.md
frontend guidelines:

- Raw <h3> → <Heading as="h3"> from @sentry/scraps/text
- SummaryGrid (display: grid) → Grid columns="repeat(6, 1fr)"
- SummaryCell, Field (flex column) → Flex direction="column"
- SummaryLabel (font-size: sm + muted color) → Text size="sm" variant="muted"
- SummaryValue (font-size: lg + bold) → Text size="lg" bold
- SecondaryText, Count (small + muted color) → Text size="sm" variant="muted"

Kept FieldLabel and TruncatedNote as thin wrappers since they layer
small layout-only tweaks (color on a <label>, margin on a <Text>) that
don't have a direct primitive equivalent. Right-align CSS specificity
fix on RightHeader/RightCell stays as-is.

Addresses Cursor Bugbot finding.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

ref(admin): migrate invoice-comparison fetch to apiOptions + useQuery

Replaces deprecated `useApiQuery` (flagged by static/AGENTS.md frontend
guide) with the new pattern: `apiOptions.as<T>()` composed into
`useQuery` from TanStack Query. Uses `skipToken` for the path arg to
disable the query before the user submits, matching the previous
`enabled` semantics.

Addresses Cursor Bugbot finding.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`sortedRows` was a no-op `useMemo` over `data.rows` — the component
never sorted client-side, it just relied on the API's sort order. The
name was misleading enough to confuse review tooling.

Rename to `rows` and inline the fallback; the endpoint sorts before
returning (see AdminInvoiceComparisonEndpoint + its test_sort_* tests)
and a code comment documents the contract.

Addresses Seer bot finding on PR #116269.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Jest 30 + SWC fails to parse scripts/genPlatformProductInfo.ts because
the source uses `const __dirname = path.dirname(fileURLToPath(import.meta.url))`
to recover __dirname under ESM. SWC's CJS transform emits that const
verbatim, which collides with the `__dirname` Node already provides via
the module wrapper — `SyntaxError: Identifier '__dirname' has already
been declared`.

This crash cascades during `--listTests --changedSince` traversal on PR
runs, surfacing as a wave of "Your test suite must contain at least one
test" errors against unrelated source files in worker logs and timing
out the Jest shards at 30 min.

Add `<rootDir>/scripts/` to both `testPathIgnorePatterns` and
`modulePathIgnorePatterns` so Jest's discovery and module resolution
both skip the directory. Nothing in scripts/ is a test, and nothing
in static/* or tests/js/* imports from scripts/ (verified via grep).

INTENT: this change should live in its own PR — it's CI infrastructure,
not part of REVENG-86. Splitting after we confirm the Jest shards pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@armcknight armcknight force-pushed the andrewmcknight/reveng-86-invoice-evaluation branch from 9db6f47 to f00b3fe Compare May 28, 2026 18:12
@armcknight armcknight requested a review from a team as a code owner May 28, 2026 18:12
@armcknight armcknight merged commit 4cc8434 into master May 28, 2026
76 checks passed
@armcknight armcknight deleted the andrewmcknight/reveng-86-invoice-evaluation branch May 28, 2026 18:18
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f00b3fe. Configure here.

query: submitted ?? undefined,
staleTime: 0,
}),
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Region change triggers unintended automatic query refetch

Medium Severity

After an initial submission, changing the region dropdown immediately fires a new query with the old date range, bypassing the "Run comparison" button. This happens because region is read live from state into the query key (path and host), while dates are gated behind the submitted state. The inconsistency means region changes auto-refetch but date changes require the button — the region needs to be captured in submitted (or submitted reset on region change) to match the intended submit-gated behavior.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f00b3fe. Configure here.

armcknight pushed a commit that referenced this pull request May 28, 2026
Jest 30 + SWC fails to parse `scripts/genPlatformProductInfo.ts`:

```
/scripts/genPlatformProductInfo.ts:51
const __dirname = _nodepath.dirname((0, _nodeurl.fileURLToPath)(require("url").pathToFileURL(__filename).toString()));
      ^
SyntaxError: Identifier '__dirname' has already been declared
```

The source uses `const __dirname = path.dirname(fileURLToPath(import.meta.url))`
to recover `__dirname` under ESM. SWC's CJS transform emits that const
verbatim, which collides with the `__dirname` Node already provides via
the module wrapper.

On PR runs where Jest goes through the `--listTests --changedSince`
path, that crash cascades — workers report a wave of "Your test suite
must contain at least one test" errors against unrelated source files
and the shards time out at 30 min. Symptom observed on
#116269 across multiple pushes; confirmed master
pushes are unaffected because they don't trigger the same traversal.

Add `<rootDir>/scripts/` to both `testPathIgnorePatterns` and
`modulePathIgnorePatterns` so Jest's discovery and module resolution
both skip the directory. Nothing in scripts/ is a test, and nothing
in static/* or tests/js/* imports from scripts/ (grep-verified).

A more durable fix would patch `scripts/genPlatformProductInfo.ts` to
rename its local `__dirname` binding (e.g. to `THIS_DIR`), but this
config change unblocks CI without modifying source files that other
infra (`pnpm gen:platform-info`) depends on.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Frontend Automatically applied to PRs that change frontend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants